package tv.geir.slackspace.analyzer;

import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;

import tv.geir.slackspace.code.PackageConfiguration;

/**
 * Analyzes bytecode and collects a list of all classes it encounters. This list can be retrieved
 * by calling <code>getDependencies()</code>.
 * 
 * @author Geir Gullestad Pettersen
 *
 */
public class ClassVisitorAnalyzer implements ClassVisitor{

	
	private ClassNameUtils utils = new ClassNameUtils();

	private Set<String> dependencies = new HashSet<String>();
	
	private static Log log = LogFactory.getLog( ClassVisitorAnalyzer.class );
	
	private PackageConfiguration config;
	
	public ClassVisitorAnalyzer(  ) {
		this.config = PackageConfiguration.getInstance();
		if( config.getPackagesToExclude().size() == 0 ) {
			config.addIgnorePackage("java.");
		}
	}
	
	public Set<String> getDependencies() {
		return dependencies;
	}
	
	
	
	public void visit(int ver, int access, String name, String signature, String superName, String[] interfaces) {
		// add constructor
		addClassName( superName );
		
		// add interfaces
		for( String i : interfaces ) {
			addClassName( i );
		}
	}

	public void visitSource(String source , String debug) {
		String s =new ToStringBuilder(null).append("source", source).append( "debug", debug ).toString();
		log.debug( s );
		
		
	}

	public void visitOuterClass(String owner, String name, String desc) {
		String s = new ToStringBuilder(null).append("owner", owner).append("name", name).append("desc", desc).toString();
		log.info( "visitOuterClass - " +s.toString() );
		addClassName( desc );
	}

	public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
		String s = new ToStringBuilder(null).append("desc", desc).append("visible", visible).toString();
		log.debug( s );
		return null;
	}

	public void visitAttribute(Attribute attr) {
		log.info("Attribute " + attr);
		
	}

	public void visitInnerClass(String name, String outerName, String innerName, int access) {
		log.info("inner: " + name);
		addClassName(name);
	}

	public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
		if( desc.charAt(0) == 'L') {
			String classname = desc.substring(1, desc.length()-1).replace('/', '.');
			addClassName(classname);
		}
		return null;
	}

	public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
		Set<String> classes = utils.getClassNamesFromSignature(desc);
		
		for (String cname : classes) {
			addClassName( cname );
		}
		return  new MethodVisitorAnalyzer( dependencies );
	}

	public void visitEnd() {
		log.debug("visit end");
	
		
	}
	
	private synchronized void addClassName( String name ) {
		name = utils.classUrlToQualifiedClassName(name);
		
		if( config.includeClass( name )) {
			dependencies.add( name );
		}
	}

	
}
